/*
 * Copyright 2008-2009 the original 赵永春(zyc@hasor.net).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.hasor.search.query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import net.hasor.search.query.params.CommonParams;
import net.hasor.search.query.params.FacetParams;
import net.hasor.search.query.params.HighlightParams;
import net.hasor.search.query.params.StatsParams;
import net.hasor.search.query.params.TermsParams;
import net.hasor.search.utils.DateUtil;
/**
 * This is an augmented SolrParams with get/set/add fields for common fields used in the Standard and Dismax request handlers
 * @since solr 1.3
 */
public class SearchQuery extends ModifiableSearchParams {
    private static final long  serialVersionUID = -4575807376396673206L;
    public static final String DOCID            = "_docid_";
    public enum ORDER {
        desc, asc;
        public ORDER reverse() {
            return (this == asc) ? desc : asc;
        }
    }
    /** Maintains a map of current sorts */
    private List<SortClause> sortClauses;
    public SearchQuery() {
        super();
    }
    /** Create a new SearchQuery
     * @param q query string
     */
    public SearchQuery(String q) {
        this();
        this.set(CommonParams.Q, q);
    }
    /** enable/disable terms.  
     * 
     * @param b flag to indicate terms should be enabled. <br /> if b==false, removes all other terms parameters
     * @return Current reference (<i>this</i>)
     */
    public SearchQuery setTerms(boolean b) {
        if (b) {
            this.set(TermsParams.TERMS, true);
        } else {
            this.remove(TermsParams.TERMS);
            this.remove(TermsParams.TERMS_FIELD);
            this.remove(TermsParams.TERMS_LOWER);
            this.remove(TermsParams.TERMS_UPPER);
            this.remove(TermsParams.TERMS_UPPER_INCLUSIVE);
            this.remove(TermsParams.TERMS_LOWER_INCLUSIVE);
            this.remove(TermsParams.TERMS_LIMIT);
            this.remove(TermsParams.TERMS_PREFIX_STR);
            this.remove(TermsParams.TERMS_MINCOUNT);
            this.remove(TermsParams.TERMS_MAXCOUNT);
            this.remove(TermsParams.TERMS_RAW);
            this.remove(TermsParams.TERMS_SORT);
            this.remove(TermsParams.TERMS_REGEXP_STR);
            this.remove(TermsParams.TERMS_REGEXP_FLAG);
        }
        return this;
    }
    public boolean getTerms() {
        return this.getBool(TermsParams.TERMS, false);
    }
    public SearchQuery addTermsField(String field) {
        this.add(TermsParams.TERMS_FIELD, field);
        return this;
    }
    public String[] getTermsFields() {
        return this.getParams(TermsParams.TERMS_FIELD);
    }
    public SearchQuery setTermsLower(String lower) {
        this.set(TermsParams.TERMS_LOWER, lower);
        return this;
    }
    public String getTermsLower() {
        return this.get(TermsParams.TERMS_LOWER, "");
    }
    public SearchQuery setTermsUpper(String upper) {
        this.set(TermsParams.TERMS_UPPER, upper);
        return this;
    }
    public String getTermsUpper() {
        return this.get(TermsParams.TERMS_UPPER, "");
    }
    public SearchQuery setTermsUpperInclusive(boolean b) {
        this.set(TermsParams.TERMS_UPPER_INCLUSIVE, b);
        return this;
    }
    public boolean getTermsUpperInclusive() {
        return this.getBool(TermsParams.TERMS_UPPER_INCLUSIVE, false);
    }
    public SearchQuery setTermsLowerInclusive(boolean b) {
        this.set(TermsParams.TERMS_LOWER_INCLUSIVE, b);
        return this;
    }
    public boolean getTermsLowerInclusive() {
        return this.getBool(TermsParams.TERMS_LOWER_INCLUSIVE, true);
    }
    public SearchQuery setTermsLimit(int limit) {
        this.set(TermsParams.TERMS_LIMIT, limit);
        return this;
    }
    public int getTermsLimit() {
        return this.getInt(TermsParams.TERMS_LIMIT, 10);
    }
    public SearchQuery setTermsMinCount(int cnt) {
        this.set(TermsParams.TERMS_MINCOUNT, cnt);
        return this;
    }
    public int getTermsMinCount() {
        return this.getInt(TermsParams.TERMS_MINCOUNT, 1);
    }
    public SearchQuery setTermsMaxCount(int cnt) {
        this.set(TermsParams.TERMS_MAXCOUNT, cnt);
        return this;
    }
    public int getTermsMaxCount() {
        return this.getInt(TermsParams.TERMS_MAXCOUNT, -1);
    }
    public SearchQuery setTermsPrefix(String prefix) {
        this.set(TermsParams.TERMS_PREFIX_STR, prefix);
        return this;
    }
    public String getTermsPrefix() {
        return this.get(TermsParams.TERMS_PREFIX_STR, "");
    }
    public SearchQuery setTermsRaw(boolean b) {
        this.set(TermsParams.TERMS_RAW, b);
        return this;
    }
    public boolean getTermsRaw() {
        return this.getBool(TermsParams.TERMS_RAW, false);
    }
    public SearchQuery setTermsSortString(String type) {
        this.set(TermsParams.TERMS_SORT, type);
        return this;
    }
    public String getTermsSortString() {
        return this.get(TermsParams.TERMS_SORT, TermsParams.TERMS_SORT_COUNT);
    }
    public SearchQuery setTermsRegex(String regex) {
        this.set(TermsParams.TERMS_REGEXP_STR, regex);
        return this;
    }
    public String getTermsRegex() {
        return this.get(TermsParams.TERMS_REGEXP_STR);
    }
    public SearchQuery setTermsRegexFlag(String flag) {
        this.add(TermsParams.TERMS_REGEXP_FLAG, flag);
        return this;
    }
    public String[] getTermsRegexFlags() {
        return this.getParams(TermsParams.TERMS_REGEXP_FLAG);
    }
    /** Add field(s) for facet computation.
     * 
     * @param fields Array of field names from the IndexSchema
     * @return this
     */
    public SearchQuery addFacetField(String... fields) {
        add(FacetParams.FACET_FIELD, fields);
        this.set(FacetParams.FACET, true);
        return this;
    }
    /** Add field(s) for pivot computation.
     * 
     * pivot fields are comma separated
     * 
     * @param fields Array of field names from the IndexSchema
     * @return this
     */
    public SearchQuery addFacetPivotField(String... fields) {
        add(FacetParams.FACET_PIVOT, fields);
        this.set(FacetParams.FACET, true);
        return this;
    }
    /**
     * Add a numeric range facet.
     *
     * @param field The field
     * @param start The start of range
     * @param end The end of the range
     * @param gap The gap between each count
     * @return this
     */
    public SearchQuery addNumericRangeFacet(String field, Number start, Number end, Number gap) {
        add(FacetParams.FACET_RANGE, field);
        add(String.format(Locale.ROOT, "f.%s.%s", field, FacetParams.FACET_RANGE_START), start.toString());
        add(String.format(Locale.ROOT, "f.%s.%s", field, FacetParams.FACET_RANGE_END), end.toString());
        add(String.format(Locale.ROOT, "f.%s.%s", field, FacetParams.FACET_RANGE_GAP), gap.toString());
        this.set(FacetParams.FACET, true);
        return this;
    }
    /**
     * Add a numeric range facet.
     *
     * @param field The field
     * @param start The start of range
     * @param end The end of the range
     * @param gap The gap between each count
     * @return this
     */
    public SearchQuery addDateRangeFacet(String field, Date start, Date end, String gap) {
        add(FacetParams.FACET_RANGE, field);
        add(String.format(Locale.ROOT, "f.%s.%s", field, FacetParams.FACET_RANGE_START), DateUtil.getThreadLocalDateFormat().format(start));
        add(String.format(Locale.ROOT, "f.%s.%s", field, FacetParams.FACET_RANGE_END), DateUtil.getThreadLocalDateFormat().format(end));
        add(String.format(Locale.ROOT, "f.%s.%s", field, FacetParams.FACET_RANGE_GAP), gap);
        this.set(FacetParams.FACET, true);
        return this;
    }
    /**
     * Add Interval Faceting on a field. All intervals for the same field should be included
     * in the same call to this method.
     * For syntax documentation see <a href="https://wiki.apache.org/solr/SimpleFacetParameters#Interval_Faceting">Solr wiki</a>
     * 
     * @param field the field to add facet intervals
     * @param intervals Intervals to be used for faceting. It can be an empty array, but it can't 
     * be <code>null</code>
     * @return this
     */
    public SearchQuery addIntervalFacets(String field, String[] intervals) {
        if (intervals == null) {
            throw new IllegalArgumentException("Can't add null intervals");
        }
        set(FacetParams.FACET, true);
        add(FacetParams.FACET_INTERVAL, field);
        for (String interval : intervals) {
            add(String.format(Locale.ROOT, "f.%s.facet.interval.set", field), interval);
        }
        return this;
    }
    /**
     * Remove all Interval Facets on a field
     * 
     * @param field the field to remove from facet intervals
     * @return Array of current intervals for <code>field</code>
     */
    public String[] removeIntervalFacets(String field) {
        while (remove(FacetParams.FACET_INTERVAL, field)) {};
        return remove(String.format(Locale.ROOT, "f.%s.facet.interval.set", field));
    }
    /** get the facet fields
     * 
     * @return string array of facet fields or null if not set/empty
     */
    public String[] getFacetFields() {
        return this.getParams(FacetParams.FACET_FIELD);
    }
    /** remove a facet field
     * 
     * @param name Name of the facet field to be removed.
     * 
     * @return true, if the item was removed. <br />
     *           false, if the facet field was null or did not exist.
     */
    public boolean removeFacetField(String name) {
        boolean b = this.remove(FacetParams.FACET_FIELD, name);
        if (this.get(FacetParams.FACET_FIELD) == null && this.get(FacetParams.FACET_QUERY) == null) {
            this.setFacet(false);
        }
        return b;
    }
    /** enable/disable faceting.  
     * 
     * @param b flag to indicate faceting should be enabled. <br /> if b==false, removes all other faceting parameters
     * @return Current reference (<i>this</i>)
     */
    public SearchQuery setFacet(boolean b) {
        if (b) {
            this.set(FacetParams.FACET, true);
        } else {
            this.remove(FacetParams.FACET);
            this.remove(FacetParams.FACET_MINCOUNT);
            this.remove(FacetParams.FACET_FIELD);
            this.remove(FacetParams.FACET_LIMIT);
            this.remove(FacetParams.FACET_MISSING);
            this.remove(FacetParams.FACET_OFFSET);
            this.remove(FacetParams.FACET_PREFIX);
            this.remove(FacetParams.FACET_QUERY);
            this.remove(FacetParams.FACET_SORT);
            this.remove(FacetParams.FACET_ZEROS);
            this.remove(FacetParams.FACET_PREFIX); // does not include the individual fields...
            this.remove(FacetParams.FACET_INTERVAL); // does not remove interval parameters
        }
        return this;
    }
    public SearchQuery setFacetPrefix(String prefix) {
        this.set(FacetParams.FACET_PREFIX, prefix);
        return this;
    }
    public SearchQuery setFacetPrefix(String field, String prefix) {
        this.set("f." + field + "." + FacetParams.FACET_PREFIX, prefix);
        return this;
    }
    /** add a faceting query
     * 
     * @param f facet query
     */
    public SearchQuery addFacetQuery(String f) {
        this.add(FacetParams.FACET_QUERY, f);
        this.set(FacetParams.FACET, true);
        return this;
    }
    /** get facet queries
     * 
     * @return all facet queries or null if not set/empty
     */
    public String[] getFacetQuery() {
        return this.getParams(FacetParams.FACET_QUERY);
    }
    /** remove a facet query
     * 
     * @param q the facet query to remove
     * @return true if the facet query was removed false otherwise
     */
    public boolean removeFacetQuery(String q) {
        boolean b = this.remove(FacetParams.FACET_QUERY, q);
        if (this.get(FacetParams.FACET_FIELD) == null && this.get(FacetParams.FACET_QUERY) == null) {
            this.setFacet(false);
        }
        return b;
    }
    /** set the facet limit
     * 
     * @param lim number facet items to return
     */
    public SearchQuery setFacetLimit(int lim) {
        this.set(FacetParams.FACET_LIMIT, lim);
        return this;
    }
    /** get current facet limit
     * 
     * @return facet limit or default of 25
     */
    public int getFacetLimit() {
        return this.getInt(FacetParams.FACET_LIMIT, 25);
    }
    /** set facet minimum count
     * 
     * @param cnt facets having less that cnt hits will be excluded from teh facet list
     */
    public SearchQuery setFacetMinCount(int cnt) {
        this.set(FacetParams.FACET_MINCOUNT, cnt);
        return this;
    }
    /** get facet minimum count
     * 
     * @return facet minimum count or default of 1
     */
    public int getFacetMinCount() {
        return this.getInt(FacetParams.FACET_MINCOUNT, 1);
    }
    /**
     * Sets facet missing boolean flag 
     * 
     * @param v flag to indicate the field of  {@link FacetParams#FACET_MISSING} .
     * @return this
     */
    public SearchQuery setFacetMissing(Boolean v) {
        this.set(FacetParams.FACET_MISSING, v);
        return this;
    }
    /** get facet sort
     * @return facet sort or default of {@link FacetParams#FACET_SORT_COUNT}
     */
    public String getFacetSortString() {
        return this.get(FacetParams.FACET_SORT, FacetParams.FACET_SORT_COUNT);
    }
    /** set facet sort
     * @param sort sort facets
     * @return this
     */
    public SearchQuery setFacetSort(String sort) {
        this.set(FacetParams.FACET_SORT, sort);
        return this;
    }
    /** add highlight field
     * @param f field to enable for highlighting
     */
    public SearchQuery addHighlightField(String f) {
        this.add(HighlightParams.FIELDS, f);
        this.set(HighlightParams.HIGHLIGHT, true);
        return this;
    }
    /** remove a field for highlighting
     * @param f field name to not highlight
     * @return <i>true</i>, if removed, <br /> <i>false</i>, otherwise
     */
    public boolean removeHighlightField(String f) {
        boolean b = this.remove(HighlightParams.FIELDS, f);
        if (this.get(HighlightParams.FIELDS) == null) {
            this.setHighlight(false);
        }
        return b;
    }
    /** get list of highlighted fields
     * @return Array of highlight fields or null if not set/empty
     */
    public String[] getHighlightFields() {
        return this.getParams(HighlightParams.FIELDS);
    }
    public SearchQuery setHighlightSnippets(int num) {
        this.set(HighlightParams.SNIPPETS, num);
        return this;
    }
    public int getHighlightSnippets() {
        return this.getInt(HighlightParams.SNIPPETS, 1);
    }
    public SearchQuery setHighlightFragsize(int num) {
        this.set(HighlightParams.FRAGSIZE, num);
        return this;
    }
    public int getHighlightFragsize() {
        return this.getInt(HighlightParams.FRAGSIZE, 100);
    }
    public SearchQuery setHighlightRequireFieldMatch(boolean flag) {
        this.set(HighlightParams.FIELD_MATCH, flag);
        return this;
    }
    public boolean getHighlightRequireFieldMatch() {
        return this.getBool(HighlightParams.FIELD_MATCH, false);
    }
    public SearchQuery setHighlightSimplePre(String f) {
        this.set(HighlightParams.SIMPLE_PRE, f);
        return this;
    }
    public String getHighlightSimplePre() {
        return this.get(HighlightParams.SIMPLE_PRE, "");
    }
    public SearchQuery setHighlightSimplePost(String f) {
        this.set(HighlightParams.SIMPLE_POST, f);
        return this;
    }
    public String getHighlightSimplePost() {
        return this.get(HighlightParams.SIMPLE_POST, "");
    }
    /**
     * Gets the raw sort field, as it will be sent to Solr. <p>
     * The returned sort field will always contain a serialized version
     * of the sort string built using {@link #setSort(SortClause)},
     * {@link #addSort(SortClause)}, {@link #addOrUpdateSort(SortClause)},
     * {@link #removeSort(SortClause)}, {@link #clearSorts()} and 
     * {@link #setSorts(List)}.
     */
    public String getSortField() {
        return this.get(CommonParams.SORT);
    }
    /**
     * Clears current sort information.
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery clearSorts() {
        sortClauses = null;
        serializeSorts();
        return this;
    }
    /**
     * Replaces the current sort information.
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery setSorts(List<SortClause> value) {
        sortClauses = new ArrayList<SortClause>(value);
        serializeSorts();
        return this;
    }
    /**
     * Gets an a list of current sort clauses.
     * @return an immutable list of current sort clauses
     * @since 4.2
     */
    public List<SortClause> getSorts() {
        if (sortClauses == null)
            return Collections.emptyList();
        else
            return Collections.unmodifiableList(sortClauses);
    }
    /**
     * Replaces the current sort information with a single sort clause
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery setSort(String field, ORDER order) {
        return setSort(new SortClause(field, order));
    }
    /**
     * Replaces the current sort information with a single sort clause
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery setSort(SortClause sortClause) {
        clearSorts();
        return addSort(sortClause);
    }
    /**
     * Adds a single sort clause to the end of the current sort information.
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery addSort(String field, ORDER order) {
        return addSort(new SortClause(field, order));
    }
    /**
     * Adds a single sort clause to the end of the query.
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery addSort(SortClause sortClause) {
        if (sortClauses == null) {
            sortClauses = new ArrayList<SortClause>();
        }
        sortClauses.add(sortClause);
        serializeSorts();
        return this;
    }
    /**
     * Updates or adds a single sort clause to the query.
     * If the field is already used for sorting, the order
     * of the existing field is modified; otherwise, it is
     * added to the end.
     * <p>
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery addOrUpdateSort(String field, ORDER order) {
        return addOrUpdateSort(new SortClause(field, order));
    }
    /**
     * Updates or adds a single sort field specification to the current sort
     * information. If the sort field already exist in the sort information map,
     * it's position is unchanged and the sort order is set; if it does not exist,
     * it is appended at the end with the specified order..
     *
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery addOrUpdateSort(SortClause sortClause) {
        if (sortClauses != null) {
            for (int index = 0; index < sortClauses.size(); index++) {
                SortClause existing = sortClauses.get(index);
                if (existing.getItem().equals(sortClause.getItem())) {
                    sortClauses.set(index, sortClause);
                    serializeSorts();
                    return this;
                }
            }
        }
        return addSort(sortClause);
    }
    /**
     * Removes a single sort field from the current sort information.
     *
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery removeSort(SortClause sortClause) {
        return removeSort(sortClause.getItem());
    }
    /**
     * Removes a single sort field from the current sort information.
     *
     * @return the modified SearchQuery object, for easy chaining
     * @since 4.2
     */
    public SearchQuery removeSort(String itemName) {
        if (sortClauses != null) {
            for (SortClause existing : sortClauses) {
                if (existing.getItem().equals(itemName)) {
                    sortClauses.remove(existing);
                    if (sortClauses.isEmpty())
                        sortClauses = null;
                    serializeSorts();
                    break;
                }
            }
        }
        return this;
    }
    private void serializeSorts() {
        if (sortClauses == null || sortClauses.isEmpty()) {
            remove(CommonParams.SORT);
        } else {
            StringBuilder sb = new StringBuilder();
            for (SortClause sortClause : sortClauses) {
                if (sb.length() > 0)
                    sb.append(",");
                sb.append(sortClause.getItem());
                sb.append(" ");
                sb.append(sortClause.getOrder());
            }
            set(CommonParams.SORT, sb.toString());
        }
    }
    public void setGetFieldStatistics(boolean v) {
        this.set(StatsParams.STATS, v);
    }
    public void setGetFieldStatistics(String field) {
        this.set(StatsParams.STATS, true);
        this.add(StatsParams.STATS_FIELD, field);
    }
    public void addStatsFieldFacets(String field, String... facets) {
        if (field == null) {
            this.add(StatsParams.STATS_FACET, facets);
        } else {
            for (String f : facets) {
                this.add("f." + field + "." + StatsParams.STATS_FACET, f);
            }
        }
    }
    public void addStatsFieldCalcDistinct(String field, boolean calcDistinct) {
        if (field == null) {
            this.add(StatsParams.STATS_CALC_DISTINCT, Boolean.toString(calcDistinct));
        } else {
            this.add("f." + field + "." + StatsParams.STATS_CALC_DISTINCT, Boolean.toString(calcDistinct));
        }
    }
    public SearchQuery setFilterQueries(String... fq) {
        this.set(CommonParams.FQ, fq);
        return this;
    }
    public SearchQuery addFilterQuery(String... fq) {
        this.add(CommonParams.FQ, fq);
        return this;
    }
    public boolean removeFilterQuery(String fq) {
        return this.remove(CommonParams.FQ, fq);
    }
    public String[] getFilterQueries() {
        return this.getParams(CommonParams.FQ);
    }
    public boolean getHighlight() {
        return this.getBool(HighlightParams.HIGHLIGHT, false);
    }
    public SearchQuery setHighlight(boolean b) {
        if (b) {
            this.set(HighlightParams.HIGHLIGHT, true);
        } else {
            this.remove(HighlightParams.HIGHLIGHT);
            this.remove(HighlightParams.FIELD_MATCH);
            this.remove(HighlightParams.FIELDS);
            this.remove(HighlightParams.FORMATTER);
            this.remove(HighlightParams.FRAGSIZE);
            this.remove(HighlightParams.SIMPLE_POST);
            this.remove(HighlightParams.SIMPLE_PRE);
            this.remove(HighlightParams.SNIPPETS);
        }
        return this;
    }
    public SearchQuery setFields(String... fields) {
        if (fields == null || fields.length == 0) {
            this.remove(CommonParams.FL);
            return this;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(fields[0]);
        for (int i = 1; i < fields.length; i++) {
            sb.append(',');
            sb.append(fields[i]);
        }
        this.set(CommonParams.FL, sb.toString());
        return this;
    }
    public SearchQuery addField(String field) {
        return addValueToParam(CommonParams.FL, field);
    }
    public String getFields() {
        String fields = this.get(CommonParams.FL);
        if (fields != null && fields.equals("score")) {
            fields = "*, score";
        }
        return fields;
    }
    public SearchQuery setIncludeScore(boolean includeScore) {
        Pattern scorePattern = Pattern.compile("(^|[, ])score");
        String fields = get(CommonParams.FL, "*");
        if (includeScore) {
            if (!scorePattern.matcher(fields).find()) {
                this.set(CommonParams.FL, fields + ",score");
            }
        } else {
            this.set(CommonParams.FL, scorePattern.matcher(fields).replaceAll(""));
        }
        return this;
    }
    public SearchQuery setQuery(String query) {
        this.set(CommonParams.Q, query);
        return this;
    }
    public String getQuery() {
        return this.get(CommonParams.Q);
    }
    public SearchQuery setRows(Integer rows) {
        if (rows == null) {
            this.remove(CommonParams.ROWS);
        } else {
            this.set(CommonParams.ROWS, rows);
        }
        return this;
    }
    public Integer getRows() {
        return this.getInt(CommonParams.ROWS);
    }
    public void setShowDebugInfo(boolean showDebugInfo) {
        this.set(CommonParams.DEBUG_QUERY, String.valueOf(showDebugInfo));
    }
    public void setDistrib(boolean val) {
        this.set(CommonParams.DISTRIB, String.valueOf(val));
    }
    public SearchQuery setStart(Integer start) {
        if (start == null) {
            this.remove(CommonParams.START);
        } else {
            this.set(CommonParams.START, start);
        }
        return this;
    }
    public Integer getStart() {
        return this.getInt(CommonParams.START);
    }
    /**
     * The Request Handler to use (see the solrconfig.xml), which is stored in the "qt" parameter.
     * Normally it starts with a '/' and if so it will be used by
     * {@link org.apache.solr.client.solrj.request.QueryRequest#getPath()} in the URL instead of the "qt" parameter.
     * If this is left blank, then the default of "/select" is assumed.
     *
     * @param qt The Request Handler name corresponding to one in solrconfig.xml on the server.
     * @return this
     */
    public SearchQuery setRequestHandler(String qt) {
        this.set(CommonParams.QT, qt);
        return this;
    }
    public String getRequestHandler() {
        return this.get(CommonParams.QT);
    }
    /**
     * @return this
     * @see ModifiableSearchParams#set(String,String[])
     */
    public SearchQuery setParam(String name, String... values) {
        this.set(name, values);
        return this;
    }
    /**
     * @return this
     * @see net.hasor.search.query.ModifiableSearchParams.solr.common.params.ModifiableSolrParams#set(String, boolean)
     */
    public SearchQuery setParam(String name, boolean value) {
        this.set(name, value);
        return this;
    }
    /** get a deep copy of this object **/
    public SearchQuery getCopy() {
        SearchQuery q = new SearchQuery();
        for (String name : this.getParameterNames()) {
            q.setParam(name, this.getParams(name));
        }
        return q;
    }
    /**
    * Set the maximum time allowed for this query. If the query takes more time
    * than the specified milliseconds, a timeout occurs and partial (or no)
    * results may be returned.
    * 
    * If given Integer is null, then this parameter is removed from the request
    * 
    *@param milliseconds the time in milliseconds allowed for this query
    */
    public SearchQuery setTimeAllowed(Integer milliseconds) {
        if (milliseconds == null) {
            this.remove(CommonParams.TIME_ALLOWED);
        } else {
            this.set(CommonParams.TIME_ALLOWED, milliseconds);
        }
        return this;
    }
    /**
    * Get the maximum time allowed for this query.
    */
    public Integer getTimeAllowed() {
        return this.getInt(CommonParams.TIME_ALLOWED);
    }
    ///////////////////////
    //  Utility functions
    ///////////////////////
    private String join(String a, String b, String sep) {
        StringBuilder sb = new StringBuilder();
        if (a != null && a.length() > 0) {
            sb.append(a);
            sb.append(sep);
        }
        if (b != null && b.length() > 0) {
            sb.append(b);
        }
        return sb.toString().trim();
    }
    private SearchQuery addValueToParam(String name, String value) {
        String tmp = this.get(name);
        tmp = join(tmp, value, ",");
        this.set(name, tmp);
        return this;
    }
    /**
     * A single sort clause, encapsulating what to sort and the sort order.
     * <p>
     * The item specified can be "anything sortable" by solr; some examples
     * include a simple field name, the constant string {@code score}, and functions
     * such as {@code sum(x_f, y_f)}.
     * <p>
     * A SortClause can be created through different mechanisms:
     * <PRE><code>
     * new SortClause("product", SearchQuery.ORDER.asc);
     * new SortClause("product", "asc");
     * SortClause.asc("product");
     * SortClause.desc("product");
     * </code></PRE>
     */
    public static class SortClause implements java.io.Serializable {
        private static final long serialVersionUID = 1L;
        private final String      item;
        private final ORDER       order;
        /**
         * Creates a SortClause based on item and order
         * @param item item to sort on
         * @param order direction to sort
         */
        public SortClause(String item, ORDER order) {
            this.item = item;
            this.order = order;
        }
        /**
         * Creates a SortClause based on item and order
         * @param item item to sort on
         * @param order string value for direction to sort
         */
        public SortClause(String item, String order) {
            this(item, ORDER.valueOf(order));
        }
        /**
         * Creates an ascending SortClause for an item
         * @param item item to sort on
         */
        public static SortClause create(String item, ORDER order) {
            return new SortClause(item, order);
        }
        /**
         * Creates a SortClause based on item and order
         * @param item item to sort on
         * @param order string value for direction to sort
         */
        public static SortClause create(String item, String order) {
            return new SortClause(item, ORDER.valueOf(order));
        }
        /**
         * Creates an ascending SortClause for an item
         * @param item item to sort on
         */
        public static SortClause asc(String item) {
            return new SortClause(item, ORDER.asc);
        }
        /**
         * Creates a decending SortClause for an item
         * @param item item to sort on
         */
        public static SortClause desc(String item) {
            return new SortClause(item, ORDER.desc);
        }
        /**
         * Gets the item to sort, typically a function or a fieldname
         * @return item to sort
         */
        public String getItem() {
            return item;
        }
        /**
         * Gets the order to sort
         * @return order to sort
         */
        public ORDER getOrder() {
            return order;
        }
        public boolean equals(Object other) {
            if (this == other)
                return true;
            if (!(other instanceof SortClause))
                return false;
            final SortClause that = (SortClause) other;
            return this.getItem().equals(that.getItem()) && this.getOrder().equals(that.getOrder());
        }
        public int hashCode() {
            return this.getItem().hashCode();
        }
        /**
         * Gets a human readable description of the sort clause.
         * <p>
         * The returned string is not suitable for passing to Solr,
         * but may be useful in debug output and the like.
         * @return a description of the current sort clause
         */
        public String toString() {
            return "[" + getClass().getSimpleName() + ": item=" + getItem() + "; order=" + getOrder() + "]";
        }
    }
}